﻿// ReSharper disable InconsistentNaming
using System;
using CI.Misc;
using NUnit.Framework;
using CI;
using CI.Interfaces;
using CI.Mesopotamia;
using CI.Organism;

namespace Mesopotamia.UnitTests
{
    /// <summary>Fitness function that evaluates and places a high score on travelling over new area and reduces on bumper hit detection on organisms Unit Testing</summary>
    [TestFixture]
    public class MNTABHDTests
    {
        private MNTABHDInherited _mntabhd;
        private readonly OrganismCollection _organisms = new OrganismCollection();
        IOrganism _organism;
        IHostingEnvironment _hostingEnvironment = new MSRBHost(new MockHostingEnvironmentPhysical(), new ScenarioLocalXML());
        long _org1Energy;
        long _org2Energy;
        long _org3Energy;
        long _org4Energy;
        IOrganism _organism1;
        IOrganism _organism2;
        IOrganism _organism3;
        IOrganism _organism4;

        public MNTABHDTests()
        {
            _org4Energy = 0;
            _org3Energy = 0;
            _org2Energy = 0;
        }

        [SetUp]
        public void Setup()
        {
            _mntabhd = new MNTABHDInherited();


            // clear the previous list
            _organisms.Alive.Clear();
            _organisms.Dead.Clear();
            _organisms.Inactive.Clear();

            _hostingEnvironment = new MSRBHost(new MockHostingEnvironmentPhysical(), new ScenarioLocalXML()) { Organisms = _organisms };

            // setup organisms
            _organism1 = CreateNewBasicOrganism();
            _organism2 = CreateNewBasicOrganism();
            _organism3 = CreateNewBasicOrganism();
            _organism4 = CreateNewBasicOrganism();

            // add a cell to the organisms
            new iRobot(_organism1, Guid.NewGuid());
            new Amoeba(_organism2, Guid.NewGuid());
            new Amoeba(_organism3, Guid.NewGuid());
            new Amoeba(_organism4, Guid.NewGuid());

            // add it to the environment
            _hostingEnvironment.RegisterPhysicalCell(_organism1.Cells[0], ActionType.AddCell);
            _hostingEnvironment.RegisterPhysicalCell(_organism2.Cells[0], ActionType.AddCell);
            _hostingEnvironment.RegisterPhysicalCell(_organism3.Cells[0], ActionType.AddCell);
            _hostingEnvironment.RegisterPhysicalCell(_organism4.Cells[0], ActionType.AddCell);

            _mntabhd.SetupMovementCount(_organism1, 50);
        }

        private IOrganism CreateNewBasicOrganism()
        {
            return new CI.Organism.Organism(_hostingEnvironment, Guid.NewGuid(), 100, 0);
        }

        #region Parameter passing tests
        /// <summary>
        /// Call the evalutate fitness with a null parameter
        /// </summary>
        [Test, ExpectedException(typeof(ArgumentException), ExpectedMessage = "organism")]
        public void EvaluateFitnessNullOrganismException()
        {
            _mntabhd.EvaluateFitnessAndUpdateInternalFitnessValue(null);
        }

        /// <summary>
        /// Call the update organisms with a null parameter
        /// </summary>
        [Test, ExpectedException(typeof(ArgumentException), ExpectedMessage = "organisms")]
        public void UpdateOrganismsNullOrganismsException()
        {
            _mntabhd.UpdateOrganisms(null);
        }
        #endregion

        /// <summary>
        /// the evaluate fitness method in max new area travelled is a reflection of the organisms energy levels as long as we have children
        /// but without movement
        /// </summary>
        [Test]
        public void EvaluateFitness_EnergyWithoutMovement_Equal()
        {
            SetupChildParentOrganisms();
            Assert.AreEqual(0, _mntabhd.EvaluateFitnessAndUpdateInternalFitnessValue(_organism), "evaluate fitness and energy level are not equal");
        }

        private void SetupChildParentOrganisms()
        {
            _organism = CreateNewBasicOrganism();
            new SimpleNeuralSystem(_organism);
            IOrganism childOrganism = CreateNewBasicOrganism();
            childOrganism.ParentOrganisms.Add(_organism);
            _organism.Energy = 123;
            _organism.Organisms.AliveAdd(childOrganism);
        }

        /// <summary>
        /// the evaluate fitness method in max new area travelled is a reflection of the organisms energy levels as long as we have children
        /// and we moved
        /// </summary>
        [Test]
        public void EvaluateFitness_EnergyWithMovement_Equal()
        {
            SetupChildParentOrganisms();
            _mntabhd.SetupMovementCount(_organism, 50);
            Assert.AreEqual(_organism.Energy, _mntabhd.EvaluateFitnessAndUpdateInternalFitnessValue(_organism), "evaluate fitness and energy level are not equal");
        }

        /// <summary>
        /// all of these organisms have moved so they will have an energy increment
        /// </summary>
        [Test]
        public void UpdateOrganisms_AliveMovmentEnergy_Increment()
        {
            // add a organisms
            _organisms.AliveAdd(_organism1);
            _organisms.AliveAdd(_organism2);
            _organisms.AliveAdd(_organism3);
            _organisms.AliveAdd(_organism4);

            const int initCIET = 23;
            _organism1.CIET = initCIET;
            _organism2.CIET = initCIET;
            _organism3.CIET = initCIET;
            _organism4.CIET = initCIET;

            // save the energy levels
            _org1Energy = ++_organism1.Energy;
            _org2Energy = ++_organism2.Energy;
            _org3Energy = ++_organism3.Energy;
            _org4Energy = ++_organism4.Energy;

            // move the organisms
            _organism1.Location = new Location(2, 2, 2);
            _organism2.Location = new Location(3, 3, 3);
            _organism3.Location = new Location(-1, -1, -1);
            _organism4.Location = new Location(-1, 1, 4);


            // change system hb else dirty locations wont recalculate
            ((MSRBHost)SimEnv.Instance.HostEnv).SystemHeartBeat = 1;

            // call the update
            _mntabhd.UpdateOrganisms(_organisms);

            long expectedChange = GetExpectedChange(initCIET);

            // make sure they have had a increase
            Assert.AreEqual(_org1Energy + expectedChange, _organism1.Energy, "Energy was not increased");
            Assert.AreEqual(_org2Energy + expectedChange, _organism2.Energy, "Energy was not increased");
            Assert.AreEqual(_org3Energy + expectedChange, _organism3.Energy, "Energy was not increased");
            Assert.AreEqual(_org4Energy + expectedChange, _organism4.Energy, "Energy was not increased");
        }

        private static long GetExpectedChange(long InitCIET)
        {
            return Convert.ToInt64(InitCIET * MNTABHD.NewLocationMultiplier);
        }

        /// <summary>
        /// all of these organisms have moved so they will have an energy increment
        /// </summary>
        [Test]
        public void UpdateOrganisms_DeadMovmentEnergy_NoChange()
        {
            // save the energy levels
            // add a organisms
            _organisms.DeadAdd(_organism1);
            _organisms.DeadAdd(_organism2);
            _organisms.DeadAdd(_organism3);
            _organisms.DeadAdd(_organism4);

            // save the energy levels
            _org1Energy = ++_organism1.Energy;
            _org2Energy = ++_organism2.Energy;
            _org3Energy = ++_organism3.Energy;
            _org4Energy = ++_organism4.Energy;

            // move the organisms
            _organism1.Location = new Location(0, 0, 1);
            _organism2.Location = new Location(0, 0, 1);
            _organism3.Location = new Location(0, 0, 1);
            _organism4.Location = new Location(0, 0, 1);

            // call the update
            _mntabhd.UpdateOrganisms(_organisms);

            // make sure they have are the same
            Assert.AreEqual(_org1Energy, _organism1.Energy, "Energy should be the same");
            Assert.AreEqual(_org2Energy, _organism2.Energy, "Energy should be the same");
            Assert.AreEqual(_org3Energy, _organism3.Energy, "Energy should be the same");
            Assert.AreEqual(_org4Energy, _organism4.Energy, "Energy should be the same");
        }

        /// <summary>
        /// all of these organisms have moved so they will have an energy increment
        /// </summary>
        [Test]
        public void UpdateOrganisms_InactiveMovmentEnergy_NoChange()
        {
            // add a organisms
            _organisms.InactiveAdd(_organism1);
            _organisms.InactiveAdd(_organism2);
            _organisms.InactiveAdd(_organism3);
            _organisms.InactiveAdd(_organism4);

            // save the energy levels
            _org1Energy = ++_organism1.Energy;
            _org2Energy = ++_organism2.Energy;
            _org3Energy = ++_organism3.Energy;
            _org4Energy = ++_organism4.Energy;

            // move the organisms
            _organism1.Location = new Location(0, 0, 1);
            _organism2.Location = new Location(0, 0, 1);
            _organism3.Location = new Location(0, 0, 1);
            _organism4.Location = new Location(0, 0, 1);

            // call the update
            _mntabhd.UpdateOrganisms(_organisms);

            // make sure they have are the same
            Assert.AreEqual(_org1Energy, _organism1.Energy, "Energy should be the same");
            Assert.AreEqual(_org2Energy, _organism2.Energy, "Energy should be the same");
            Assert.AreEqual(_org3Energy, _organism3.Energy, "Energy should be the same");
            Assert.AreEqual(_org4Energy, _organism4.Energy, "Energy should be the same");
        }


        /// <summary>
        /// fire a bumper hit on an organism which should cause an energy decrease
        /// </summary>
        [Test]
        public void UpdateOrganisms_BumperHit_EnergyDecrease()
        {
            // add a organisms
            _organisms.AliveAdd(_organism1);

            // save the energy levels
            _org1Energy = _organism1.Energy;
            // give it a CIET
            _organism1.CIET = 20;

            // change system hb else dirty locations wont recalculate
            ((MSRBHost)SimEnv.Instance.HostEnv).SystemHeartBeat = 2;

            // fire a bumper hit to do this add a neuron to the waiting list with an input synapse with name "bumper" in it
            var sns = new SimpleNeuralSystem(_organism1);
            INeuron neuron1 = new StepNeuron(_organism1, Guid.NewGuid());

            _organism1.Location = new Location(0, 0, 1);

            var synapseIO = new SynapseIO("bumper", "shortName", "desc", Guid.NewGuid());

            ISynapse syanpse1 = new SimpleSynapse(Guid.NewGuid(), sns);
            syanpse1.AddSynapticConnectionSource(synapseIO);
            syanpse1.AddSynapticConnectionTarget(neuron1);


            neuron1.InputSynapse.Add(syanpse1);

            sns.AddNeuron(neuron1);
            // fire synapse
            synapseIO.FireInput(100);
            sns.Evaluate();

            // call the update
            _mntabhd.UpdateOrganisms(_organisms);

            // make sure they have had a decrease by CIET for the hit and increase of CIET*Multiplier value
            Assert.AreEqual(_org1Energy - (_organism1.CIET - GetExpectedChange(_organism1.CIET)), _organism1.Energy, "Energy is not correct");

        }

        #region Evalute Fitness
        /// <summary>
        /// Call the fittest ordered list and with a null parameter
        /// </summary>
        [Test, ExpectedException(typeof(ArgumentException), ExpectedMessage = "organisms")]
        public void EvaluateFitness_NullOrganism_Exception()
        {
            _mntabhd.Fittest(null);
        }

        /// <summary>
        /// Evalute the fitness of the organism, if it has not children it should be zero
        /// </summary>
        [Test]
        public void EvaluateFitness_NoChildren_ZeroFitness()
        {
            const long _testValue = 400;


            // give it a neural system
            new SimpleNeuralSystem(_organism1);

            // set the energy
            _organism1.Energy = _testValue;

            // check the result
            Assert.AreEqual(0, _mntabhd.EvaluateFitnessAndUpdateInternalFitnessValue(_organism1));
            // check it saved it to the fitness value
            Assert.AreEqual(0, _organism1.FitnessValue);
        }

        /// <summary>
        /// Evalute the fitness of the organism and consider its children
        /// </summary>
        [Test]
        public void EvaluateFitness_WithChildren_Equal()
        {
            const long _testValue = 400;

            // create the organism
            _organism1.Organisms.AliveAdd(_organism1);
            // give it a neural system
            new SimpleNeuralSystem(_organism1);

            // create 2 children
            IOrganism organismChild1 = CreateNewBasicOrganism();
            IOrganism organismChild2 = CreateNewBasicOrganism();
            organismChild1.ParentOrganisms.Add(_organism1);
            organismChild2.ParentOrganisms.Add(_organism1);

            // add them all to the same organism colleciton in the environment
            organismChild1.Organisms.AliveAdd(organismChild1);
            organismChild2.Organisms.DeadAdd(organismChild2);

            // set the energy
            _organism1.Energy = _testValue;

            // check the result, start value plus the 2 children
            Assert.AreEqual(_testValue + 200 - _organism1.InitEnergy, _mntabhd.EvaluateFitnessAndUpdateInternalFitnessValue(_organism1));
            // check it saved it to the fitness value
            Assert.AreEqual(_testValue + 200 - _organism1.InitEnergy, _organism1.FitnessValue);
        }
        #endregion
    }


    public class MNTABHDInherited : MNTABHD
    {
        public void SetupMovementCount(IOrganism organism, int count)
        {
            OrganismMovementCount[organism.Guid] = count;
        }
    }
}
